使用libSSH2实现与Linux Shell的命令行交互

您所在的位置:网站首页 linux shell交互式命令退格 使用libSSH2实现与Linux Shell的命令行交互

使用libSSH2实现与Linux Shell的命令行交互

2024-07-12 16:47| 来源: 网络整理| 查看: 265

使用Linux已经有不少年头,也使用过不少Linux的SSH工具,比如SecureCRT,XShell,Putty,SmartTTY,但都未发现有一个工具可以像Windows资源管理器一样操作Linux下的文件的工具,SecureCRT的同门软件SecureFX有那么点感觉,浏览目录、文件结构还是挺不错的,但是在打开、编辑文件上还是不太流畅。

于是就想着如果自己能搞一个出来应该会很不错。那首先得使用SSH协议连接上服务器,网上查了一下,发现libSSH2还不错,文档,示例这些都有,但是没有完整的命令行交互示例。在example中有一个ssh2.c的示例给出了一个框架:

1 /* Open a SHELL on that pty */ 2 if (libssh2_channel_shell(channel)) 3 { 4 fprintf(stderr, "Unable to request shell on allocated pty\n"); 5 goto shutdown; 6 } 7 8 /* At this point the shell can be interacted with using 9 * libssh2_channel_read() 10 * libssh2_channel_read_stderr() 11 * libssh2_channel_write() 12 * libssh2_channel_write_stderr() 13 * 14 * Blocking mode may be (en|dis)abled with: libssh2_channel_set_blocking() 15 * If the server send EOF, libssh2_channel_eof() will return non-0 16 * To send EOF to the server use: libssh2_channel_send_eof() 17 * A channel can be closed with: libssh2_channel_close() 18 * A channel can be freed with: libssh2_channel_free() 19 */ 20 21skip_shell: 22 if (channel) 23 { 24 libssh2_channel_free(channel); 25 channel = NULL; 26 } 27 28 /* Other channel types are supported via: 29 * libssh2_scp_send() 30 * libssh2_scp_recv2() 31 * libssh2_channel_direct_tcpip() 32 */

这个框架只是以注释的形式给出了一些API,说在那个位置可以使用这些API来实现与Shell的交互。 但是如果只是单独使用这些API可能只能进行一个回合的交互,达不到一个完整的像见的ssh工具那样输入一个命令后,显示结果,再等待输入下一个命令的效果。

笔者结合另一个示例ssh2_echo.c,添加使用libssh2_poll来监测channel的读写,实现了简单的命令行交互(不支持像top这样可以刷新的命令):

1enum state 2{ 3 INCOMPLETED, 4 COMPLETED, 5 TIMEOUT 6}; 7 8ssize_t handle_read(LIBSSH2_CHANNEL *channel, char *buffer, size_t buf_size, enum state *state, int timeout) 9{ 10 LIBSSH2_POLLFD fds; 11 fds.type = LIBSSH2_POLLFD_CHANNEL; 12 fds.fd.channel = channel; 13 fds.events = LIBSSH2_POLLFD_POLLIN | LIBSSH2_POLLFD_POLLOUT; 14 15 ssize_t read_size = 0; 16 while (timeout > 0) 17 { 18 int rc = (libssh2_poll(&fds, 1, 10)); 19 if (rc < 1) 20 { 21 timeout -= 10; 22 usleep(10000); 23 continue; 24 } 25 26 if (fds.revents & LIBSSH2_POLLFD_POLLIN) 27 { 28 int n = libssh2_channel_read(channel, &buffer[read_size], buf_size - read_size); 29 if (n == LIBSSH2_ERROR_EAGAIN) 30 { 31 continue; 32 } 33 else if (n < 0) 34 { 35 *state = COMPLETED; 36 return read_size; 37 } 38 else 39 { 40 read_size += n; 41 if (libssh2_channel_eof(channel)) 42 { 43 *state = COMPLETED; 44 return read_size; 45 } 46 char end = buffer[read_size - 2]; 47 if (end == '$' || end == '#') 48 { 49 *state = COMPLETED; 50 return read_size; 51 } 52 } 53 if (read_size == buf_size) 54 { 55 *state = INCOMPLETED; 56 return read_size; 57 } 58 } 59 usleep(10000); 60 timeout -= 10; 61 } 62 63 *state = TIMEOUT; 64 return 0; 65} 66 67void handle_loop(LIBSSH2_CHANNEL *channel) 68{ 69 char buffer[8192]; 70 char cmd[64]; 71 int len = 0; 72 ssize_t n; 73 while (1) 74 { 75 enum state state = INCOMPLETED; 76 do 77 { 78 n = handle_read(channel, buffer, sizeof(buffer) - 1, &state, 3000); 79 if (state == TIMEOUT) 80 { 81 if (len > 0) 82 { 83 cmd[len - 1] = 0; 84 } 85 printf("exec cmd:`%s` timeout\n", cmd); 86 break; 87 } 88 buffer[n] = 0; 89 if (len > 0) 90 { 91 printf("%s", &buffer[len + 1]); 92 len = 0; 93 } 94 else 95 { 96 printf("%s", buffer); 97 } 98 } while (state == INCOMPLETED); 99 100 fgets(cmd, sizeof(cmd), stdin); 101 len = strlen(cmd); 102 libssh2_channel_write(channel, cmd, len); 103 } 104 libssh2_channel_close(channel); 105}

只需要skip_shell前面调用handle_loop即可。这里还实现了检测读取超时和读取Buffer是否写满,如果写满了则输出了再读取。

这里需要注意libssh2_poll接口,在第一次调用时,一般不会有LIBSSH2_POLLFD_POLLIN事件,需要再次调用才会有,跟了一下代码,第一次调用时在接收数据,准备好数据后,但并没有设置LIBSSH2_POLLFD_POLLIN事件,所以需要循环读取,libssh2_poll的超时参数有的情况不起作用,参见代码:

1if(active_fds) { 2 /* Don't block on the sockets if we have channels/listeners which 3 are ready */ 4 timeout_remaining = 0; 5 }

注意需要在支持颜色的Shell终端中执行,否则会有各种颜色标识输出,会干扰正常信息。 可以在VSCode终端、MinGW终端中执行。

以上代码在MinGW64下测试通过。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3